# encoding: utf-8
"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lane.chang on '15/04/2024'
    comment: 底层类
"""
from datetime import datetime
from decimal import Decimal
from enum import Enum


import sqlalchemy.sql.schema
from sqlalchemy import MetaData, inspect, Integer, DateTime, Date, Time, DECIMAL, Boolean
from sqlalchemy.ext.declarative import as_declarative

from project.core.exception import BizError


metadata = MetaData()


@as_declarative(metadata=metadata)
class BaseModel(object):
    # 修改剔除的列
    __edit_exclude_fields__ = ()
    # 扩展字段
    __edit_extra_fields__ = ()

    @classmethod
    def _get_columns(cls) -> list:
        """ 得到所有列
        :return:
        """
        return list(cls.__table__.columns.keys())

    @classmethod
    def field_exists(cls, filed_name) -> bool:
        """ 判断某列是否存在
        """
        return filed_name in cls._get_columns()

    def dict(self, *args, reverse: bool = False, exclude_undisplayed=True, filter_none=False) -> dict:
        """ model转换成dict(按照orm字段映射)
        :param args:
        :param reverse: 默认为False。 若为True, 则实现将args指定的字段剔除后对model进行dict化
        :param exclude_undisplayed: 过滤掉不显示字段
        :param filter_none: 是否过滤none值
        :return:
        """
        columns = set()
        _columns = self._get_columns()
        for v in dir(self):
            if v.startswith('_'):
                continue

            if not hasattr(self, v):
                continue

            # 过滤可执行对象
            if callable(getattr(self, v)):
                continue

            if isinstance(getattr(self, v), (sqlalchemy.sql.schema.MetaData, sqlalchemy.orm.decl_api.registry,)):
                continue

            if args and v not in args and not reverse:
                continue

            if args and v in args and reverse:
                continue

            # 过滤none值(如果是对象结构[list, dict], 则判断是否有值)
            _v = getattr(self, v)
            if filter_none and (_v is None or (isinstance(_v, (list, dict)) and not _v)):
                continue

            columns.add(v)

        model_dict = {column: getattr(self, column) for column in columns}
        return model_dict

    def set_model_value(self, params=None, **kwargs):
        """设置model 的值(按照orm字段映射)
        :param params:
        :return:
        """
        params = dict() if not params else params
        params.update(kwargs)
        for k, v in params.items():
            if not hasattr(self.__class__, k):
                continue
            column = getattr(self.__class__, k)
            # 做好几种特殊的处理
            if isinstance(column, property):
                # 如果不是列, 而是属性, 这种情况是自定义额外的column, 比如密码, 就直接赋值
                setattr(self, k, v)
                continue
            if column.primary_key and (v == 0 or v == ''):
                # 如果是主键, 并且设置为0 或者为空
                setattr(self, k, v)
                continue
            if v == '' and column.nullable:
                # 如果为空, 并且该字段是可为null的, 则改为None
                setattr(self, k, None)
                continue
            if v is None and column.nullable:
                # 如果为null, 并且该字段是可为null的, 则改为None
                setattr(self, k, None)
                continue
            # 值不为空, 需要做一些类型转换
            if isinstance(column.type, Integer):
                # 数值型, 需要考虑为空的情况, 还需要考虑该字段允许不允许为空
                v = int(v) if v else 0
            elif isinstance(column.type, DECIMAL):
                # Decimal 类型, 需要考虑为空的情况, 还需要考虑该字段允许不允许为空
                v = Decimal(v) if v else 0
            elif isinstance(column.type, DateTime):
                # 日期时间(DT) 2016-1-1 9:18:10
                if isinstance(v, str):
                    v = datetime.strptime(v, '%Y-%m-%d %H:%M:%S')
                elif isinstance(v, Integer):
                    v = datetime.fromtimestamp(v)
            elif isinstance(column.type, Date):
                # 日期 2016-1-1
                if isinstance(v, str):
                    v = datetime.strptime(v, "%Y-%m-%d").date()
            elif isinstance(column.type, Time):
                # 时间: 9:18:10
                if isinstance(v, str):
                    sl = v.split(':')
                    if len(sl) == 3:
                        v = datetime.strptime(v, "%H:%M:%S").time()
                    elif len(sl) == 2:
                        v = datetime.strptime(v, "%H:%M").time()
                    elif len(sl) == 1:
                        v = datetime.strptime(v, "%H").time()
            elif isinstance(column.type, Boolean):
                # 布尔
                if not v:
                    v = False
                elif isinstance(v, str):
                    v = True if v.lower() in ('true', 't') else False
                elif isinstance(v, int):
                    v = True if v else False
                else:
                    v = False

            # 支持枚举类型
            if isinstance(v, Enum):
                self.__setattr__(k, v.value)

            self.__setattr__(k, v)

        return self


class CustomModel(BaseModel):
    """ model - 表操作维度
    """
    __abstract__ = True

    @classmethod
    def get_model(cls, session, model_id):
        """ 通过ID获得model
        :param model_id:
        :param session:
        :return:
        """
        if not model_id:
            return None
        model = session.query(cls).get(model_id)
        return model

    @classmethod
    def _query(cls, session, fields_dict=None, **kwargs):
        """ 根据条件查询
        :param session:
        :param fields_dict:
        :param kwargs:
        :return:
        """
        fields_dict = dict() if not fields_dict else fields_dict
        fields_dict.update(kwargs)

        query = session.query(cls)
        for field_name, field_value in fields_dict.items():
            query = query.filter(getattr(cls, field_name) == field_value)

        return query

    @classmethod
    def query_one(cls, session, fields_dict=None, **kwargs):
        """ 查询一条数据
        :param session:
        :param fields_dict:
        :param kwargs:
        :return:
        """
        model = cls._query(fields_dict=fields_dict, session=session, **kwargs).first()

        return model

    @classmethod
    def query_all(cls, session, fields_dict=None, order_by=None, **kwargs):
        """ 查询多条数据(支持排序)
        :param session:
        :param fields_dict:
        :param order_by:
        :param kwargs:
        :return:
        """
        query = cls._query(fields_dict=fields_dict, session=session, **kwargs)
        for v in order_by if order_by else ():
            query = query.order_by(v)
        models = query.all()

        return models

    @classmethod
    def create_modify(cls, session, model_dict=None, uniques=(), unique_err='', no_to_create=True, commit=True, **kwargs):
        """ 新增或修改(提交)
        :param session:
        :param model_dict:
        :param commit: 是否提交
        :param uniques: 唯一字段 field_name 列表, 会去验证是否重复
        :param unique_err: 是否弹出数据重复检查异常
        :param kwargs:
        :param no_to_create: 没有就新增
        :return:
        """
        pri_key = inspect(cls).primary_key[0].name
        model_dict = dict() if not model_dict else model_dict
        model_dict.update(kwargs)
        model_id = model_dict.get(pri_key)

        # 验证数据是否重复
        for unique in uniques:
            fields_dict = dict()
            for field_name in unique:
                field_value = model_dict.get(field_name)
                if field_value is None:
                    raise BizError('唯一值不能为空')
                fields_dict[field_name] = field_value

            cm = cls.query_one(session, fields_dict)
            if cm and getattr(cm, pri_key) != model_id:
                if unique_err:
                    raise BizError(unique_err)
                return cm

        # 新建修改
        if not model_id:
            if not no_to_create:
                return
            m = cls()
            session.add(m)
        else:
            m = session.query(cls).get(model_id)
            if not m:
                if not no_to_create:
                    return
                # 这里是为了兼容, 指定主键值, 比如manager之类的model, 他们的主键值由member的id决定
                m = cls()
                session.add(m)
        m.set_model_value(model_dict)

        if commit:
            session.commit()
        else:
            session.flush()

        return m

    def update_self(self, session, model_dict=None, commit=True, **kwargs):
        """ 自我更新
        :param session:
        :param model_dict:
        :param commit:
        :return:
        """
        model_dict = dict() if not model_dict else model_dict
        model_dict.update(kwargs)
        self.set_model_value(model_dict)
        if commit:
            session.commit()
        else:
            session.flush()
        return self

    def delete_self(self, session, commit=True):
        """ 自我更新
        :param session:
        :param commit:
        :return:
        """
        session.delete(self)
        if commit:
            session.commit()
        else:
            session.flush()


